---
title: Using and Binding UIScript
---

While developers can adjust custom material properties in the editor, they can also specify callback behaviors for data changes using the `UIScript` directive. By leveraging hook functions exposed by UIScript, developers can implement property interdependencies, reducing the number of declared properties and simplifying the Inspector panel.

### Binding `UIScript` in ShaderLab:

```glsl showLineNumbers
Editor {
    ...
    UIScript "/path/to/script";
    ...
}
```

The bound `UIScript` script path supports both relative and absolute paths. Using the project root directory as an example, the absolute path is `/PBRScript1.ts` and the relative path is `./PBRScript1.ts`.

<Image
  src="https://mdn.alipayobjects.com/huamei_aftkdx/afts/img/A*t4LFQ4KEL6kAAAAAAAAAAAAADteEAQ/fmt.webp"
  width="70%"
/>

### UIScript Interface

The editor exposes relevant APIs through the built-in `ShaderUIScript` class. The type definition of `ShaderUIScript` is embedded in the Galacean Web editor, with the complete definition as follows:

```ts
import { Color, Material, Texture, Vector2, Vector3, Vector4 } from "@galacean/engine";

type ShaderPropertyValue = number | Vector2 | Vector3 | Vector4 | Color | Texture;
type ShaderMacroValue = number | Vector2 | Vector3 | Vector4 | Color;

/**
 * Script for extending `Shader` UI logic.
 */
export abstract class ShaderUIScript {
  /** @internal */
  _propertyCallBacks: Map<string, (material: Material, value: ShaderPropertyValue) => void> = new Map();

  /** @internal */
  _macroCallBacks: Map<string, (material: Material, defined: boolean, value: ShaderMacroValue) => void> = new Map();

  /**
   * Called when the shader is switched.
   * @param material - The material the shader is bound to
   */
  onMaterialShaderSwitched(material: Material): void {}

  /**
   * Registers a property change callback.
   * @param propertyName - Property name
   * @param onChanged - Callback triggered when the property changes
   */
  protected onPropertyChanged(
    propertyName: string,
    onChanged: (material: Material, value: ShaderPropertyValue) => void
  ): void {
    this._propertyCallBacks.set(propertyName, onChanged);
  }

  /**
   * Registers a macro change callback.
   * @param macroName - Macro name
   * @param onChanged - Callback triggered when the macro changes
   */
  protected onMacroChanged(
    macroName: string,
    onChanged: (material: Material, defined: boolean, value: ShaderMacroValue) => void
  ): void {
    this._macroCallBacks.set(macroName, onChanged);
  }
}
```

### Writing UIScript

1. Create a UIScript in the editor.

<Image
  src="https://mdn.alipayobjects.com/huamei_aftkdx/afts/img/A*Qh4UTZgaY7MAAAAAAAAAAAAADteEAQ/fmt.webp"
  width="60%"
  figcaption="Creating UIScript"
/>

2. Specify property change callbacks by extending the `ShaderUIScript` class.

```ts
import { Material, RenderQueueType, Vector3, BlendFactor, RenderFace, CullMode, BlendMode } from "@galacean/engine";

export default class extends ShaderUIScript {
  constructor() {
    super();

    // Register property change listeners in the constructor
    this.onPropertyChanged("material_NormalTexture", (material: Material, value) => {
      const shaderData = material.shaderData;
      if (value) {
        shaderData.enableMacro("MATERIAL_HAS_NORMALTEXTURE");
      } else {
        shaderData.disableMacro("MATERIAL_HAS_NORMALTEXTURE");
      }
    });
  }

  // Override shader binding callback
  override onMaterialShaderSwitched(material: Material) {
    const shaderData = material.shaderData;

    shaderData.disableMacro("MATERIAL_OMIT_NORMAL");
    shaderData.enableMacro("MATERIAL_NEED_WORLD_POS");
    shaderData.enableMacro("MATERIAL_NEED_TILING_OFFSET");

    // Set default values
    const anisotropyInfo = shaderData.getVector3("material_AnisotropyInfo");

    if (!anisotropyInfo) {
      shaderData.setVector3("material_AnisotropyInfo", new Vector3(1, 0, 0));
    } else {
      shaderData.setFloat("anisotropy", anisotropyInfo.z);
      const PI2 = Math.PI * 2;
      const rotationRad = (Math.atan2(anisotropyInfo.y, anisotropyInfo.x) + PI2 ) % PI2;
      shaderData.setFloat("anisotropyRotation", rotationRad * (180 / Math.PI))
    }
  }
}
```

<Callout info="warning">
Note: The current version of ShaderLab's material property module only defines the Inspector UI panel for materials bound to this shader in the editor. It does not automatically declare corresponding global variables in `ShaderPass`. If these variables are referenced in `ShaderPass` code, you must explicitly declare them in the [global variables](./shader/#Global_Variables) module.
</Callout>